/*
 * ESWrapper.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import java.util.Enumeration;
import java.util.Hashtable;

import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.exceptions.NoSuchMethodException;
import org.scriptme.ecmascript.exceptions.ProgrammingError;
import org.scriptme.ecmascript.interpreter.Evaluator;
import org.scriptme.ecmascript.interpreter.ScopeChain;

/**
 * Wrap a Java Object for use in EcmaScript.
 */
public class ESWrapper extends ESObject {

	// For debugging
	/** The debug event. */
	static boolean debugEvent = false;

	/**
	 * Sets the debug event.
	 * 
	 * @param b
	 *            the new debug event
	 */
	public static void setDebugEvent(boolean b) {
		debugEvent = b;
	}

	/**
	 * Checks if is debug event.
	 * 
	 * @return true, if is debug event
	 */
	static public boolean isDebugEvent() {
		return debugEvent;
	}

	/** The java object. */
	private Object javaObject; // the wrapped object

	/** The as bean. */
	private boolean asBean = false; // true if created as a bean

	// A marker object never returned as a valid property !
	/** The no property marker. */
	private static ESObject noPropertyMarker = null;

	/** The event handlers. */
	private Hashtable eventHandlers = null;

	/** The event adaptors. */
	private Hashtable eventAdaptors = null;

	/**
	 * Wrap a java object in an EcmaScript object, use object and beans
	 * reflection to access the java object fields.
	 * 
	 * @param javaObject
	 *            the Java object to wrap
	 * @param evaluator
	 *            the evaluator
	 */
	public ESWrapper(Object javaObject, Evaluator evaluator) {
		super(null, evaluator);
		this.javaObject = javaObject;
		if (javaObject.getClass().isArray()) {
			throw new ProgrammingError("Object wrapper used on array object");
		}
		// This should be done at startup
		if (noPropertyMarker == null) {
			noPropertyMarker = new ObjectObject(null, evaluator);
		}
	}

	/**
	 * Wrap a java object in an EcmaScript object, use only beans reflection to
	 * access the java object fields if asBean is true.
	 * 
	 * @param javaObject
	 *            the Java object to wrap
	 * @param evaluator
	 *            the evaluator
	 * @param asBean
	 *            if true always consider the object as a bean
	 */
	public ESWrapper(Object javaObject, Evaluator evaluator, boolean asBean) {
		super(null, evaluator);
		this.javaObject = javaObject;
		this.asBean = asBean;
		if (javaObject.getClass().isArray()) {
			throw new ProgrammingError("Object wrapper used on array object");
		}
		// This should be done at startup
		if (noPropertyMarker == null) {
			noPropertyMarker = new ObjectObject(null, evaluator);
		}
	}

	/**
	 * Return the wraped object.
	 * 
	 * @return the wraped object
	 */
	public Object getJavaObject() {
		return javaObject;
	}

	/**
	 * Return true if object must be considered as a java bean.
	 * 
	 * @return true if bean
	 */
	public boolean isBean() {
		return asBean;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getPrototype()
	 */
	public ESObject getPrototype() {
		throw new ProgrammingError("Cannot get prototype of Wrapper");
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getESClassName()
	 */
	public String getESClassName() {
		return "Java Object";
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getTypeOf()
	 */
	public int getTypeOf() {
		return EStypeObject;
	}

	// overrides
	// Get either a bean or an object property - for objects attempt bean access
	// if object access failed
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getPropertyInScope(java.lang.String,
	 *      org.scriptme.ecmascript.interpreter.ScopeChain, int)
	 */
	public ESValue getPropertyInScope(String propertyName,
			ScopeChain previousScope, int hash) throws EcmaScriptException {
		ESValue value;
		// if (ESLoader.debugJavaAccess) System.out.println("** Property
		// searched in scope: " + propertyName);
		if (asBean) {
			value = getBeanProperty(propertyName, hash);
			if (value != noPropertyMarker)
				return value; // found
		} else {
			value = getObjectProperty(propertyName, hash);
			if (value == noPropertyMarker)
				value = getBeanProperty(propertyName, hash);
			if (value == noPropertyMarker)
				value = getCorbaProperty(propertyName, hash);
		}
		if (value == noPropertyMarker) {
			if (previousScope == null) {
				throw new EcmaScriptException("global variable '"
						+ propertyName + "' does not have a value");
			} else {
				value = previousScope.getValue(propertyName, hash);
			}
		}
		return value;
	}

	// overrides
	// Get either a bean or an object property - for objects attempt bean access
	// if object access failed
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getProperty(java.lang.String,
	 *      int)
	 */
	public ESValue getProperty(String propertyName, int hash)
			throws EcmaScriptException {
		ESValue value;

		if (asBean) {
			// If declared as bean only examine using bean convention
			value = getBeanProperty(propertyName, hash);
			if (value == noPropertyMarker) {
				throw new EcmaScriptException("Property '" + propertyName
						+ "' does not exists in bean " + this);
				// return ESUndefined.theUndefined;
			}
		} else {
			// Otherwise examine as java object, bean, or corba object
			value = getObjectProperty(propertyName, hash);
			if (value == noPropertyMarker)
				value = getBeanProperty(propertyName, hash);

			if (value == noPropertyMarker && javaObject instanceof Class) {
				/*
				 * THIS CODE HAS BEEN REPLACED BY A HACK USING THE $ FORM OF THE
				 * NAME, SEE BELOW Class insideClasses[] = ((Class)
				 * javaObject).getDeclaredClasses(); //
				 * System.out.println("***** insideClasses.size:
				 * "+insideClasses.length); if (insideClasses.length>0) { throw
				 * new EcmaScriptException("Handling of subclasses not
				 * implemented - sorry"); // return ESUndefined.theUndefined; }
				 * else { throw new EcmaScriptException("Subclass, field or
				 * property '" + propertyName + "' does not exists in class " +
				 * this + " - Subclass search not implemented in JDK 1.1");
				 * //return ESUndefined.theUndefined; }
				 */
				// getDeclaredClasses is not implemented, use name to find
				// internal classes
				String className = ((Class) javaObject).getName() + "$"
						+ propertyName;
				if (ESLoader.debugJavaAccess)
					System.out
							.println("** Check if inside class: " + className);

				Class insideClass = null;
				try {
					insideClass = Class.forName(className);
				} catch (ClassNotFoundException ex) {
					throw new EcmaScriptException(
							"Subclass, field or property '" + propertyName
									+ "' does not exists in class " + this);
					// return ESUndefined.theUndefined;
				}
				return new ESWrapper(insideClass, evaluator);
			}
			if (value == noPropertyMarker)
				value = getCorbaProperty(propertyName, hash);

			if (value == noPropertyMarker) {
				throw new EcmaScriptException("Field or property '"
						+ propertyName + "' does not exists in object " + this);
			}
		}

		return value;
	}

	/**
	 * Get a bean property (static property of the class if applied to a Class
	 * object).
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param hash
	 *            its hash value
	 * 
	 * @return the bean property an EcmaScript value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private ESValue getBeanProperty(String propertyName, int hash)
			throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** Bean property searched: " + propertyName);

		Class cls = null;
		Object theObject = null; // means static
		if (javaObject instanceof Class) {
			cls = (Class) javaObject;
		} else {
			cls = (Class) javaObject.getClass();
			theObject = javaObject;
		}

		return ESLoader.normalizeValue(theObject, evaluator);
	}

	/**
	 * Get a corba property (use name of property as name of the routine).
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param hash
	 *            its hash value
	 * 
	 * @return the bean property an EcmaScript value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private ESValue getCorbaProperty(String propertyName, int hash)
			throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** CORBA property searched: " + propertyName);
		Class cls = (Class) javaObject.getClass();

		if (ESLoader.debugJavaAccess)
			System.out.println("** CORBA read method found for: "
					+ propertyName);

		Object obj = null;
		return ESLoader.normalizeValue(obj, evaluator);
	}

	/**
	 * Get an object property (static property of the class if applied to a
	 * Class object).
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param hash
	 *            its hash value
	 * 
	 * @return the bean property an EcmaScript value
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private ESValue getObjectProperty(String propertyName, int hash)
			throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out
					.println("** Java object field searched: " + propertyName);

		Class cls = null;
		Object theObject = null; // means static
		if (javaObject instanceof Class) {
			cls = (Class) javaObject;
		} else {
			cls = (Class) javaObject.getClass();
			theObject = javaObject;
		}

		Object obj = null;
		return ESLoader.normalizeValue(obj, evaluator);
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#hasProperty(java.lang.String,
	 *      int)
	 */
	public boolean hasProperty(String propertyName, int hash)
			throws EcmaScriptException {
		// TEST - wont work for functions, just for fields.
		try {
			getProperty(propertyName, hash);
		} catch (Exception e) {
			return false;
		}
		return true; // So it can be dereferenced by scopechain
		// and wont be created
		// See deleteProperty too and 8.6.2 on host objects
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#isHiddenProperty(java.lang.String,
	 *      int)
	 */
	public boolean isHiddenProperty(String propertyName, int hash) {
		return false;
	}

	// overrides
	// Put either a bean or an object property - for objects attempt bean access
	// if object access failed
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#putProperty(java.lang.String,
	 *      org.scriptme.ecmascript.data.ESValue, int)
	 */
	public void putProperty(String propertyName, ESValue propertyValue, int hash)
			throws EcmaScriptException {
		if (propertyValue == ESUndefined.theUndefined) {
			throw new EcmaScriptException("Cannot set the field or property "
					+ propertyName
					+ " of a non EcmaScript object field to undefined");
		}

		if (asBean) {
			if (!putBeanProperty(propertyName, propertyValue, hash)) {
				throw new EcmaScriptException("Cannot put value in property '"
						+ propertyName
						+ "' which does not exists in Java Bean '" + this + "'");
			}
		} else {
			if (putObjectProperty(propertyName, propertyValue, hash))
				return;
			if (putBeanProperty(propertyName, propertyValue, hash))
				return;
			if (putCorbaProperty(propertyName, propertyValue, hash))
				return;

			throw new EcmaScriptException(
					"Cannot put value in field or property '"
							+ propertyName
							+ "' which does not exists in Java or Corba object '"
							+ this + "'");
		}
	}

	/**
	 * Put a property using the beans access functions.
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param propertyValue
	 *            the property value
	 * @param hash
	 *            the hash
	 * 
	 * @return true, if put bean property
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @propertyValue the value of the property
	 * @hash the hash code of the property name
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private boolean putBeanProperty(String propertyName, ESValue propertyValue,
			int hash) throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** Bean property searched: " + propertyName);
		if (propertyValue == ESUndefined.theUndefined) {
			throw new ProgrammingError("Cannot set bean property "
					+ propertyName + " to undefined");
		}
		Class cls = null;
		Object theObject = null; // means static
		if (javaObject instanceof Class) {
			cls = (Class) javaObject;
		} else {
			cls = (Class) javaObject.getClass();
			theObject = javaObject;
		}

		if (ESLoader.debugJavaAccess)
			System.out.println("** Property set: " + propertyName);

		return true;
	}

	/**
	 * Put a property using the CORBA convention.
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param propertyValue
	 *            the property value
	 * @param hash
	 *            the hash
	 * 
	 * @return true, if put corba property
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @propertyValue the value of the property
	 * @hash the hash code of the property name
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private boolean putCorbaProperty(String propertyName,
			ESValue propertyValue, int hash) throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** Corba property searched: " + propertyName);

		if (propertyValue == ESUndefined.theUndefined) {
			throw new ProgrammingError("Cannot set non EcmaScript property "
					+ propertyName + " to undefined");
		}

		if (ESLoader.debugJavaAccess)
			System.out.println("** Property set: " + propertyName);
		return true;
	}

	/**
	 * Put a property using the beans access functions. If not found and starts
	 * by "on" assume that it is an event handler.
	 * 
	 * @param propertyName
	 *            the name of the property
	 * @param propertyValue
	 *            the property value
	 * @param hash
	 *            the hash
	 * 
	 * @return true, if put object property
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @propertyValue the value of the property
	 * @hash the hash code of the property name
	 * @exception NoSuchFieldException
	 *                There is no such property
	 * @exception EcmaScriptException
	 *                Error accessing the property value
	 */
	private boolean putObjectProperty(String propertyName,
			ESValue propertyValue, int hash) throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** Object field searched: " + propertyName);

		if (propertyValue == ESUndefined.theUndefined) {
			throw new ProgrammingError("Cannot set java object field "
					+ propertyName + " to undefined");
		}
		Object theObject = null; // means static
		Class cls = null;
		if (javaObject instanceof Class) {
			cls = (Class) javaObject;
		} else {
			cls = (Class) javaObject.getClass();
			theObject = javaObject;
		}

		return true;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#putHiddenProperty(java.lang.String,
	 *      org.scriptme.ecmascript.data.ESValue)
	 */
	public void putHiddenProperty(String propertyName, ESValue propertyValue)
			throws EcmaScriptException {
		throw new ProgrammingError("Cannot put hidden property in " + this);
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#deleteProperty(java.lang.String,
	 *      int)
	 */
	public boolean deleteProperty(String propertyName, int hash)
			throws EcmaScriptException {
		// Well, in fact it should use a hasProperty, but as
		// it is not well implemented...
		return false;
	}

	/**
	 * indicates that the getProperties return an enumerator to the value itself
	 * rather than to the index.
	 * 
	 * Return true
	 * 
	 * @return true, if checks if is direct enumerator
	 */
	public boolean isDirectEnumerator() {
		return true;
	}

	/**
	 * If this is an enumerator, use it directly as an enumerator for "for in"
	 * statements. It is a "bizare" enumerator, as it returns directly the
	 * element to enumerate rather than its index in the object itself.
	 * <P>
	 * Otherwise returns an dummy enumerator.
	 * 
	 * @return the enumerator or a dummy enumerator
	 */
	public Enumeration getProperties() {
		if (javaObject instanceof Enumeration) {
			return (Enumeration) javaObject;
		} else {
			// No visible properties supported - yet
			return new Enumeration() {
				public boolean hasMoreElements() {
					return false;
				}

				public Object nextElement() {
					throw new java.util.NoSuchElementException();
				}
			};
		}
	}

	// No property list supported - yet
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getAllProperties()
	 */
	public Enumeration getAllProperties() {
		return getProperties();
	}

	/**
	 * Create an event handler for a specific property.
	 * 
	 * @param propertyName
	 *            the name of the property (must start with "on")
	 * @param handler
	 *            The value which must be an event handler routine or script
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	private void putEventHandler(String propertyName, ESValue handler)
			throws EcmaScriptException {
		String eventName = propertyName.substring(2);
		if (debugEvent)
			System.out
					.println("** Attempt to set event '" + propertyName + "'");

		// prepare the handler code - if it is not a function,
		// build a function of one argument (the event)
		ESObject eventHandler = null;
		if (handler != ESNull.theNull) {
			if (handler instanceof FunctionPrototype) {
				eventHandler = (ESObject) handler;
			} else {
				ESValue body = handler.toESString();
				ESObject fo = evaluator.getFunctionObject();
				ESValue event = new ESString("event"); // The parameter of the
														// handling routine
				ESValue[] esArgs = new ESValue[] { event, body };
				try {
					eventHandler = fo.doConstruct(null, esArgs);
				} catch (EcmaScriptException e) {
					// The error must be rethrown as if it is the error
					// indicating incmplete
					// lines the interpreter may ask the user for more lines !
					throw new EcmaScriptException(
							"Error creating function anonymous(event){" + body
									+ "}\n" + e);
				}
			}
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getDefaultValue(int)
	 */
	public ESValue getDefaultValue(int hint) throws EcmaScriptException {
		if (hint == EStypeString) {
			return new ESString(javaObject.toString());
		} else {
			throw new EcmaScriptException("No default value for " + this
					+ " and hint " + hint);
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getDefaultValue()
	 */
	public ESValue getDefaultValue() throws EcmaScriptException {
		return this.getDefaultValue(EStypeString);
	}

	/**
	 * Find a method
	 * <P>
	 * If static return null instead of exception in case of not found at all.
	 * 
	 * @param evaluator
	 *            theEvaluator
	 * @param functionName
	 *            the name to find
	 * @param params
	 *            The list of parameters to filter by type
	 * @param staticMethod
	 *            the static method
	 * 
	 * @return the method (null if static and not found)
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @staticMethod true if looking for a static method
	 * @exception NoSuchMethodException
	 *                instance object and method not found
	 * @exception EcmaScriptException
	 *                various errors
	 */
	private Object lookupMethod(Evaluator evaluator, String functionName,
			Object[] params, boolean staticMethod) throws EcmaScriptException {
		return null;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doIndirectCall(org.scriptme.ecmascript.interpreter.Evaluator,
	 *      org.scriptme.ecmascript.data.ESObject, java.lang.String,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESValue doIndirectCall(Evaluator evaluator, ESObject target,
			String functionName, ESValue[] arguments)
			throws EcmaScriptException {

		ESValue eobj;

		return ESUndefined.theUndefined;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doIndirectCallInScope(org.scriptme.ecmascript.interpreter.Evaluator,
	 *      org.scriptme.ecmascript.interpreter.ScopeChain,
	 *      org.scriptme.ecmascript.data.ESObject, java.lang.String, int,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESValue doIndirectCallInScope(Evaluator evaluator,
			ScopeChain previousScope, ESObject thisObject, String functionName,
			int hash, ESValue[] arguments) throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out
					.println("** Method searched (indirect): " + functionName);

		try {
			return doIndirectCall(evaluator, thisObject, functionName,
					arguments);
		} catch (EcmaScriptException e) {
			if (previousScope == null) {
				throw new EcmaScriptException("no global function named '"
						+ functionName + "'");
			} else {
				return previousScope.doIndirectCall(evaluator, thisObject,
						functionName, hash, arguments);
			}
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#callFunction(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		return constructOrCall(thisObject, arguments, true);
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doConstruct(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		return constructOrCall(thisObject, arguments, false);
	}

	/**
	 * Implements both new and function call on class objects (which create a
	 * new object). The only difference is that elementary objects are not
	 * normalized to EcmaScript objects if 'new' is used, so that the can be
	 * operated upon as java object. Mostly useful for String
	 * 
	 * @param thisObject
	 *            the target object of the call
	 * @param arguments
	 *            the arguments
	 * @param isCall
	 *            true if call, false if new
	 * 
	 * @return the result of the new or call
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 * 
	 * @exception EmcaScriptException
	 *                any exception during call
	 */
	private ESObject constructOrCall(ESObject thisObject, ESValue[] arguments,
			boolean isCall) throws EcmaScriptException {
		if (ESLoader.debugJavaAccess)
			System.out.println("** Constructor searched for: "
					+ javaObject.toString());

		if (javaObject instanceof Class) {
			int nArgs = arguments.length;
			try {
				Class cls = (Class) javaObject;
				/*
				 * Constructor [] constructors = cls.getConstructors(); Object[]
				 * params = new Object[nArgs]; for (int k = 0; k<nArgs; k++) {
				 * if (arguments[k] == ESUndefined.theUndefined) { throw new
				 * EcmaScriptException( "Cannot use undefined as parameter for
				 * java constructor " + cls.toString()); } params[k] =
				 * arguments[k].toJavaObject(); } // To inform user if a
				 * constructor of proper name and attribute found but not
				 * matching arguments boolean atLeastOneFoundWithAttributes =
				 * false; Constructor nearestConstructorFound = null;
				 * CompatibilityDescriptor descriptorOfNearestConstructorFound =
				 * null; int distanceOfNearestConstructorFound = -1; // infinite
				 * boolean multipleAtSameDistance = false; for (int i=0; i<constructors.length;
				 * i++) { Constructor constructor = constructors[i]; if
				 * (ESLoader.debugJavaAccess) System.out.println( "** Contructor
				 * examined: " + constructor.toString()); Class[] paramTypes =
				 * constructor.getParameterTypes(); int modifiers =
				 * constructor.getModifiers(); if
				 * (!Modifier.isPublic(modifiers)) continue;
				 * atLeastOneFoundWithAttributes = true; if
				 * (paramTypes.length!=nArgs) continue; CompatibilityDescriptor
				 * cd = ESLoader.areParametersCompatible(paramTypes, params);
				 * int distance = cd.getDistance(); if (distance<0) continue; //
				 * Constructor not applicable if (ESLoader.debugJavaAccess)
				 * System.out.println("** Constructor acceptable(" + distance + " : " +
				 * Modifier.toString(modifiers) + " " +
				 * constructors[i].toString()); // Optimization - if perfect
				 * match return immediately // Note that "perfect" match could
				 * be wrong if there is // a bug in the complex parameter
				 * matching algorithm, // so the optimization is not taken when
				 * debugging, to // allow to catch multiple "perfect match"
				 * which should not happen. if (distance == 0 &&
				 * !ESLoader.debugJavaAccess) { nearestConstructorFound =
				 * constructor; descriptorOfNearestConstructorFound = cd;
				 * distanceOfNearestConstructorFound = distance; break; //
				 * success } // Imperfect match, keep the best one if multiple
				 * found if (nearestConstructorFound == null) { // None so far
				 * nearestConstructorFound = constructor;
				 * descriptorOfNearestConstructorFound = cd;
				 * distanceOfNearestConstructorFound = distance; } else { //
				 * Keep best - if identical we have a problem if (distance<distanceOfNearestConstructorFound) {
				 * nearestConstructorFound = constructor;
				 * descriptorOfNearestConstructorFound = cd;
				 * distanceOfNearestConstructorFound = distance;
				 * multipleAtSameDistance = false; } else if (distance ==
				 * distanceOfNearestConstructorFound) { if
				 * (ESLoader.debugJavaAccess) System.out.println("** Same
				 * distance as previous constructor!"); if (distance!=0) { // if
				 * 0 (due to debugging) accept any good one
				 * multipleAtSameDistance = true; } } } } // We have found if
				 * (nearestConstructorFound!=null) { if (multipleAtSameDistance) {
				 * throw new EcmaScriptException("Ambiguous constructor for "+
				 * javaObject.toString() ); }
				 * descriptorOfNearestConstructorFound.convert(params); if
				 * (ESLoader.debugJavaAccess) System.out.println( "** Contructor
				 * called: " + nearestConstructorFound.toString()); Object obj =
				 * null; try { obj =
				 * nearestConstructorFound.newInstance(params); } catch
				 * (InvocationTargetException e) { throw new
				 * EcmaScriptException("Error creating " + javaObject + ": " +
				 * e.getTargetException()); } if (ESLoader.isBasicClass(cls) &&
				 * !isCall) { return new ESWrapper(obj, evaluator); // Do not
				 * normalize if new basic class } else { return
				 * ESLoader.normalizeObject(obj, evaluator); } }
				 * 
				 * if (atLeastOneFoundWithAttributes) { throw new
				 * EcmaScriptException("No constructor matching parameters in: "+
				 * this); } else { throw new EcmaScriptException("No public
				 * constructor in: "+ this); }
				 */
			} catch (Exception e) {
				throw new EcmaScriptException("Cannot build new " + this
						+ ", error: " + e.toString());
			}
		} else {
			throw new EcmaScriptException("Not a java class: " + this);
		}
		return null;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doubleValue()
	 */
	public double doubleValue() {
		double d = Double.NaN; // should check if doubleValue is present
		return d;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#booleanValue()
	 */
	public boolean booleanValue() {
		return true; // Should check if booleanValue is present
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#toString()
	 */
	public String toString() {
		return (javaObject == null) ? "<?Wrapper to null?>" : javaObject
				.toString();
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#toJavaObject()
	 */
	public Object toJavaObject() {
		return javaObject;
	}

	// Routine to handle events
	/**
	 * Dispatch event.
	 * 
	 * @param a
	 *            the a
	 * @param listener
	 *            the listener
	 * @param event
	 *            the event
	 */
	public void dispatchEvent(Object[] a, Class listener,
			Object /* Method */event) {
		/*
		 * if (debugEvent) System.out.println(" ** Dispatch event: " +
		 * event.getName() + " for " + listener.getName()); if (eventHandlers ==
		 * null) return; // no handler at all
		 * 
		 * String key = listener.getName() + ":"+ event.getName(); if
		 * (debugEvent) System.out.println(" ** Event key: " + key);
		 * 
		 * ESObject handlerFunction = (ESObject) eventHandlers.get(key); if
		 * (handlerFunction == null) return; // no handler for this event
		 * 
		 * if (debugEvent) System.out.println(" ** Handler found: " +
		 * handlerFunction);
		 * 
		 * try { evaluator.evaluateEvent(this, handlerFunction, a); } catch
		 * (EcmaScriptException e) { System.err.println("Exception in FESI event
		 * handler: " + e); }
		 */
	}

	// Routines to describe this object

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#toDetailString()
	 */
	public String toDetailString() {
		if (asBean)
			return "ES:[BEAN:" + getESClassName() + ":" + javaObject.toString()
					+ "]";
		else
			return "ES:[OBJ:" + getESClassName() + ":" + javaObject.toString()
					+ "]";
	}

	/** The Constant stepClass. */
	static final int stepClass = 0;

	/** The Constant stepConstructors. */
	static final int stepConstructors = 1;

	/** The Constant stepMethods. */
	static final int stepMethods = 2;

	/** The Constant stepBeanMethods. */
	static final int stepBeanMethods = 3;

	/** The Constant stepFields. */
	static final int stepFields = 4;

	/** The Constant stepBeanProperties. */
	static final int stepBeanProperties = 5;

	/** The Constant stepEvents. */
	static final int stepEvents = 6;

	/** The Constant stepNoMore. */
	static final int stepNoMore = 7;

	/**
	 * Gets the events.
	 * 
	 * @param cls
	 *            the cls
	 * 
	 * @return the events
	 */
	private Object[] getEvents(Class cls) {
		/*
		 * EventSetDescriptor [] eds = null; BeanInfo bi = null; try { bi =
		 * Introspector.getBeanInfo(cls); } catch (IntrospectionException e) { //
		 * ignore events } if (bi!=null) eds = bi.getEventSetDescriptors(); if
		 * (eds==null) { eds = new EventSetDescriptor[0]; } return eds;
		 */
		return null;
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getAllDescriptions()
	 */
	public Enumeration getAllDescriptions() {
		return null;
		/*
		 * return new Enumeration() { Class clazz = javaObject.getClass(); int
		 * step = stepClass; Constructor [] constructors =
		 * clazz.getConstructors(); Method [] methods = (asBean ? new Method[0] :
		 * clazz.getMethods()); Field[] fields = (asBean ? new Field[0] :
		 * clazz.getFields()); PropertyDescriptor [] beanProperties =
		 * getBeanPropertyDescriptors(); MethodDescriptor [] beanMethods =
		 * getBeanMethodDescriptors(); EventSetDescriptor [] events =
		 * ESWrapper.this.getEvents(clazz); int index = 0;
		 * 
		 * private PropertyDescriptor [] getBeanPropertyDescriptors() {
		 * PropertyDescriptor [] bean_properties = new PropertyDescriptor[0]; if
		 * (ESWrapper.this.asBean) { try { BeanInfo beanInfo =
		 * Introspector.getBeanInfo(clazz); bean_properties =
		 * beanInfo.getPropertyDescriptors(); } catch (Exception e) { // ignore } }
		 * else { // Only return them if different from fields...
		 * PropertyDescriptor [] properties = null; try { BeanInfo beanInfo =
		 * Introspector.getBeanInfo(clazz); properties =
		 * beanInfo.getPropertyDescriptors(); } catch (Exception e) { // ignore }
		 * if (properties!=null) { Field[] fields = clazz.getFields(); // This
		 * is pretty uggly... int remainingProps = 0; for (int iProps = 0;
		 * iProps<properties.length; iProps++) { String propName =
		 * properties[iProps].getName(); for (int iField = 0; iField<fields.length;
		 * iField++) { String fieldName = fields[iField].getName(); if
		 * (propName.equals(fieldName)) { properties[iProps] = null; break; } }
		 * if (properties[iProps] != null) remainingProps++; } if
		 * (remainingProps>0) { bean_properties = new
		 * PropertyDescriptor[remainingProps]; int insert = 0; for (int iProps =
		 * 0; iProps<properties.length; iProps++) { if
		 * (properties[iProps]!=null) bean_properties[insert++] =
		 * properties[iProps]; } } } } return bean_properties; }
		 * 
		 * private MethodDescriptor [] getBeanMethodDescriptors() {
		 * MethodDescriptor [] bean_methods = new MethodDescriptor[0]; if
		 * (ESWrapper.this.asBean) { try { BeanInfo beanInfo =
		 * Introspector.getBeanInfo(clazz); bean_methods =
		 * beanInfo.getMethodDescriptors(); } catch (Exception e) { // ignore } }
		 * return bean_methods; }
		 * 
		 * public boolean hasMoreElements() { if (step==stepClass) return true;
		 * if (step==stepConstructors) { if (constructors.length>index) return
		 * true; step ++; index = 0; } if (step==stepMethods) { if
		 * (methods.length>index) return true; step ++; index = 0; } if
		 * (step==stepBeanMethods) { if (beanMethods.length>index) return true;
		 * step ++; index = 0; } if (step==stepFields) { if
		 * (fields.length>index) return true; step ++; index = 0; } if
		 * (step==stepBeanProperties) { if (beanProperties.length>index) return
		 * true; step ++; index = 0; } if (step==stepEvents) { if
		 * (events.length>index) return true; step ++; index = 0; } return
		 * false; } public Object nextElement() { if (hasMoreElements()) {
		 * switch (step) { case stepClass: { step ++; if (asBean) { return new
		 * ValueDescription("BEAN", describe_class_or_interface(clazz)); } else {
		 * return new ValueDescription("CLASS",
		 * describe_class_or_interface(clazz)); } } case stepConstructors: { if
		 * (asBean) { String info = "[[error]]"; index++; try { BeanInfo
		 * beanInfo = Introspector.getBeanInfo(clazz); BeanDescriptor
		 * beanDescriptor = beanInfo.getBeanDescriptor(); info =
		 * beanDescriptor.getName() + " (" +
		 * beanDescriptor.getShortDescription() + ")"; } catch (Exception e) { //
		 * ignore } return new ValueDescription("BEANINFO", info); } else {
		 * return new ValueDescription("CONSTR",
		 * describe_method_or_constructor(constructors[index++])); } } case
		 * stepMethods: { return new ValueDescription("FUNC",
		 * describe_method_or_constructor(methods[index++])); } case
		 * stepBeanMethods: { return new ValueDescription("METHOD",
		 * describe_bean_method(beanMethods[index++])); } case stepFields: {
		 * return new ValueDescription("FIELD", describe_field(fields[index++],
		 * javaObject)); } case stepBeanProperties: { return new
		 * ValueDescription("PROPS",
		 * describe_bean_property(beanProperties[index++], javaObject)); } case
		 * stepEvents: { return new ValueDescription("EVENT",
		 * describe_event(events[index++])); } } // switch throw new
		 * ProgrammingError("Inconsistent step"); } else { throw new
		 * java.util.NoSuchElementException(); } } };
		 */
	}

	/**
	 * Returns a full description of the value, with the specified name.
	 * 
	 * @param name
	 *            The name of the value to describe
	 * 
	 * @return the description of this value
	 */
	public ValueDescription getDescription(String name) {
		return new ValueDescription(name, "JAVAOBJ", this.toString());
	}

	/**
	 * Return the name of an interface or primitive type, handling arrays.
	 * 
	 * @param t
	 *            the t
	 * 
	 * @return the string
	 */
	private static String typename(Class t) {
		String brackets = "";
		while (t.isArray()) {
			brackets += "[]";
			t = t.getClass() /* getComponentType() */;
		}
		return t.getName() + brackets;
	}

	/**
	 * Return a string version of modifiers, handling spaces nicely.
	 * 
	 * @param m
	 *            the m
	 * 
	 * @return the string
	 */
	private static String modifiers(int m) {
		if (m == 0)
			return "";
		else
			return ""; /* Modifier.toString(m) + " " */
	}
}
